/* Copyright (c) 2007-2008, Jeffrey R Griffin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Jeffrey R Griffin nor the
*       names of his contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Jeffrey R Griffin ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package net.visibleblue.android.paranoid;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.crypto.digests.MD5Digest;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.ContentURI;
import android.provider.BaseColumns;

public class Alice {
	
	private int _id = 0;
	private int _contactID = 0;
	private String _keyAlgorithm = null;
	private Context _context = null;
	private static CharSequence BEGIN_PK = "-----BEGIN PRIVATE KEY-----";
	private static CharSequence END_PK = "-----END PRIVATE KEY-----";
	private static final Charset CHARSET = Charset.forName("ISO-8859-1");
	private static final CharsetDecoder DECODER = CHARSET.newDecoder();
	
	private Alice(){}
	
	public static Alice createFromNonSecureMedium(byte[] encoded, Context context){
		String digest = getMD5Digest64(encoded);
		String[] projection = new String[] {
			    BaseColumns._ID,
			    Provider.CONTACT_ID_COLUMN,
			    Provider.DIGEST_COLUMN,
			    Provider.PUBLIC_KEY_ALGORITHM_COLUMN
			};
		String selection = Provider.DIGEST_COLUMN+" = '"+digest+"'";
		/*Provider pd = new Provider();
		pd.onCreate();
		Cursor cursor = pd.query(Provider.CONTENT_URI, projection, selection, null, null, null, 
				Provider.DEFAULT_SORT_ORDER);*/
		Cursor cursor = context.getContentResolver().query(Provider.CONTENT_URI, projection, 
					selection, null, Provider.DEFAULT_SORT_ORDER);
		if(cursor.count()==0)
			return null;
		cursor.moveTo(0);
		Alice alice = new Alice();
		alice._context = context;
		alice._id = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID));
		alice._contactID = cursor.getInt(cursor.getColumnIndex(Provider.CONTACT_ID_COLUMN));
		alice._keyAlgorithm = 
			cursor.getString(cursor.getColumnIndex(Provider.PUBLIC_KEY_ALGORITHM_COLUMN));
		return alice;
	}
	
	public String getPublicKeyAlgorithm()
	{
		return _keyAlgorithm;
	}
	
	public PublicKey getPublicKey() throws NoSuchAlgorithmException
	{
		return getPublicKey(getPublicKeyEncoded(), _keyAlgorithm);
	}
	
	public byte[] getPublicKeyEncoded()
	{
		InputStream stream = null;
		try
		{
			ContentResolver resolver = _context.getContentResolver();
			stream = resolver.openInputStream(getPublicKeyURI());
		}
		catch(FileNotFoundException fnfEx){}
		byte[] buffer = null;
		try
		{
			buffer = new byte[stream.available()];
			stream.read(buffer);
			stream.close();
		}
		catch(IOException ioEx){return null;}
		return buffer;
	}
	
	public ContentURI getPublicKeyURI()
	{
		ContentURI uri = Provider.CONTENT_URI;
		uri = uri.addId(_id);
		return uri;
	}
	
	public String getContactName()
	{
		if(_contactID==0)
			return "Unknown Entity";
		else
		{
			String[] projection = new String[] {
				    android.provider.Contacts.PeopleColumns.NAME
				};
			Cursor cursor = _context.getContentResolver().query(
					android.provider.Contacts.People.CONTENT_URI.addId(_contactID), projection, 
					null, null,  android.provider.Contacts.People.DEFAULT_SORT_ORDER);
			if(cursor.count()==0)
				return "Unknown Entity";
			cursor.first();
			String name = cursor.getString(cursor.getColumnIndex(
					android.provider.Contacts.PeopleColumns.NAME));
			cursor.deactivate();
			return name;
		}
	}
	
	public static String getMD5Digest64(byte[] data){
		MD5Digest md = new MD5Digest();
		md.update(data, 0, data.length);
		byte[] digest = new byte[md.getDigestSize()];
		md.doFinal(digest, 0);
		return getBase64Encoding(digest);
	}

	static boolean verifyMD5KeyDigest(String digest64, byte[] key){
		String newDigest64 = getMD5Digest64(key);
		return digest64.equals(newDigest64);
	}

	static String getBase64Encoding(byte[] bytes)
	{
		return Charset.defaultCharset().decode(
				ByteBuffer.wrap(Base64.encodeBase64(bytes))).toString();

	}

	static byte[] decodeBase64Encoding(String decode)
	{
		return Base64.decodeBase64(decode.getBytes());
	}

	public static PublicKey getPublicKey(byte[] encoded, String algorithm)
		throws NoSuchAlgorithmException
	{
		try
		{
			//check for PEM encoding
			CharBuffer chars = DECODER.decode(ByteBuffer.wrap(encoded));
			if(chars.subSequence(0, BEGIN_PK.length()).toString().equals(BEGIN_PK))
			{
				StringBuilder builder = new StringBuilder(chars);
				builder.delete(0, BEGIN_PK.length());
				builder.delete(builder.indexOf(END_PK.toString()), builder.length());
				encoded = decodeBase64Encoding(builder.toString());
			}
		}
		catch(CharacterCodingException ccEx){}
		PublicKey key = null;
		//is it just an ecoded key?
		X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
		KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
		try
		{
			key = keyFactory.generatePublic(spec);
		}
		catch(InvalidKeySpecException iksEx){}
		if(key==null)
		{
			try
			{
				//or is it a certificate? (PEM or BER)
				CertificateFactory cf = CertificateFactory.getInstance("X.509");
				Collection<? extends Certificate> c = 
					cf.generateCertificates(new ByteArrayInputStream(encoded));
				Iterator<? extends Certificate> i = c.iterator();
				while (i.hasNext()) {
					Certificate cert = (Certificate)i.next();
					key = cert.getPublicKey();
					if(key!=null)
						return key;
				}
			}
			catch(CertificateException certEx){}
		}
		return key;
	}
}
